package cn.lwpframework.mvc;

import cn.lwpframework.annotation.Autowired;
import cn.lwpframework.annotation.Controller;
import cn.lwpframework.annotation.RequestMapping;
import cn.lwpframework.annotation.Service;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;

/**
 * description:
 *
 * @author lanwp
 * @date 2018/9/15 0:18
 */
public class LwpDispatch extends HttpServlet{

    private Properties pp = new Properties();

//    private Map<String, Class<?>> ioc = new HashMap<>();
    private Map<String, Object> ioc = new HashMap<>();

    private Map<String, Method> urlMapping = new HashMap<>();

    private String suffix = "";

    public LwpDispatch() {
        super();
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
        // 6、方法执行invoke
        invoker(req, resp);
    }

    @Override
    public void destroy() {
        super.destroy();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        // 1、加载配置
        doLoadConfig(config);
        // 2、IOC容器初始化
        // 3、扫描包，并加载类
        doScanPackage();

        // 4、进行DI操作依赖注入
        doDi();

        // 5、mvc url和方法映射
        handlerMapping();

        System.out.println("初始化加载完成");
    }

    private void doDi() {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object instance = entry.getValue();
            Class clazz = instance.getClass();
            // 注入属性,返回直接在该元素上的注释
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                // 强制访问
                declaredField.setAccessible(true);
                Object value = ioc.get(declaredField.getType().getName());
                try {
                    declaredField.set(instance, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void invoker(HttpServletRequest req, HttpServletResponse resp) {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 取得映射路径
        String urlMappingKey = requestURI.replaceFirst(contextPath, "");
        Method method = urlMapping.get(urlMappingKey);
        if (method == null) {
//            throw new RuntimeException("映射地址没有对应的处理方法")
            return;
        }

        //简单参数处理
        Map<String, String[]> parameterMap = req.getParameterMap();
//        method.getParameterTypes();
        Parameter[] parameters = method.getParameters();
        List objectArray = new ArrayList();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            String parameterName = parameter.getName();
            Class<?> parameterType = parameter.getType();
            String[] parameterValue = parameterMap.get(parameterName);
            if (parameterType == req.getClass() || parameterType.isAssignableFrom(req.getClass())) {
                objectArray.add(req);
            } else if (parameterType == resp.getClass() || parameterType.isAssignableFrom(resp.getClass())) {
                objectArray.add(resp);
            } else if (parameterType == HttpSession.class || parameterType.isAssignableFrom(HttpSession.class)) {
                objectArray.add(req.getSession(false));
            } else {
                /*DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
                String[] parameterNames = discover.getParameterNames(method);*/
                if (parameterType.isArray()) {
                    objectArray.add(parameterMap.get(parameterName));
                } else {
                    if (parameterType == String.class) {
                        String value = req.getParameter(parameterName);
                        objectArray.add(value);
                    } else {
                        objectArray.add(req.getParameter(parameterName));
                    }
                }
            }
        }
        try {
            // 方法所在的类获取实例
            method.invoke(ioc.get(method.getDeclaringClass().getName()),  objectArray.toArray());
//            method.invoke(ioc.get(method.getDeclaringClass().getName()), new Object[]{req, resp, req.getParameter("name")} );
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private boolean isBlank(String str){
        if (str == null || str.length() == 0 || str.trim().length() == 0) {
            return true;
        }
        return false;
    }

    private void handlerMapping() {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object instance = entry.getValue();
            Class clazz = instance.getClass();
            // 取得类上面的requestMapping
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                String[] paths = getRequestMappingPath((RequestMapping) clazz.getAnnotation(RequestMapping.class));

                // 注入属性,返回直接在该元素上的注释
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (!declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        continue;
                    }
                    String[] methodPaths = getRequestMappingPath(declaredMethod.getAnnotation(RequestMapping.class));
                    for (int i = 0; i < paths.length; i++) {
                        String path = handlePath(paths[i]);
                        for (int i1 = 0; i1 < methodPaths.length; i1++) {
                            String pathM = handlePath(methodPaths[i]);
                            String pathKey = path + pathM;
                            if (urlMapping.containsKey(pathKey)) {
                                throw new RuntimeException(String.format("已经存在相同的url mapping地址, 重复url: {}", pathKey));
                            }
                            urlMapping.put(path + pathM + suffix, declaredMethod);
                        }
                    }
                }
            } else {
                // 注入属性,返回直接在该元素上的注释
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (!declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        continue;
                    }
                    String[] methodPaths = getRequestMappingPath(declaredMethod.getAnnotation(RequestMapping.class));
                    for (int i = 0; i < methodPaths.length; i++) {
                        String pathM = handlePath(methodPaths[i]);
                        if (urlMapping.containsKey(pathM)) {
                            throw new RuntimeException(String.format("已经存在相同的url mapping地址, 重复url: {}", pathM));
                        }
                        urlMapping.put(pathM + suffix, declaredMethod);
                    }
                }
            }
        }
    }

    /**
     * 处理path中的/
     * @param path
     * @return
     */
    private String handlePath(String path) {
        path = path.replaceAll("/+", "/");
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return path;
    }

    private String[] getRequestMappingPath(RequestMapping requestMapping) {
        String[] paths = requestMapping.value();
        if (paths == null || paths.length == 0) {
            paths = requestMapping.path();
        }
        return paths;
    }


    private void doScanPackage() {
        String scanPackage =  pp.getProperty("scanPackage");
        scanPackage = scanPackage.replace(".", "/");
        URL packageUrl = this.getClass().getClassLoader().getResource(scanPackage);
        File file = new File(packageUrl.getPath());
        listFiles(file);
    }

    private void listFiles(File file) {
        File[] fs = file.listFiles();	//遍历path下的文件和目录，放在File数组中
        for(File f:fs){					//遍历File[]数组
            if(f.isDirectory()){
                listFiles(f);
            } else {
                // 获得当前文件的类名-- 比较复杂,可以参考tom
                File file1 = new File(this.getClass().getClassLoader().getResource("/").getPath());
                // 获得类
                String className = f.getPath().replace(".class", "")
                        .replace(file1.getPath() + File.separator, "").replace(File.separator, ".");
                try {
                    Class<?> clazz = Class.forName(className);
                    // 只实例注解的类
                    if (clazz.isAnnotationPresent(Controller.class)) {
                        if (ioc.containsKey(clazz.getName())) {
                            throw new RuntimeException(String.format("实例化相同的类 {}", clazz.getName()));
                        }
                        ioc.put(clazz.getName(), clazz.newInstance());
                    } else if (clazz.isAnnotationPresent(Service.class)) {
                        Class<?>[] interfaces = clazz.getInterfaces();
                        Object instance = clazz.newInstance();
                        for (Class<?> anInterface : interfaces) {
                            if (ioc.containsKey(anInterface.getName())) {
                                throw new RuntimeException(String.format("实例化相同的类 {}", clazz.getName()));
                            }
                            ioc.put(anInterface.getName(), instance);
                        }
                        ioc.put(clazz.getName(), instance);
                    } else {
                        continue;
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doLoadConfig(ServletConfig config) {
        String configFile = config.getInitParameter("configLocation");
        InputStream configIn = null;
        try {
            configIn = this.getClass().getClassLoader().getResourceAsStream(configFile);
            pp.load(configIn);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (configIn != null) {
                try {
                    configIn.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // 取得路径后缀
        if (pp.getProperty("mvc.suffix") != null) {
            suffix = pp.getProperty("mvc.suffix");
        }
    }
}
